﻿using Microsoft.Extensions.Logging;
using Odyssey.Consul;
using Odyssey.Consul.Features;
using Odyssey.Consul.Models;
using Odyssey.Consul.Models.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Athena.Consul
{
    using Options;
    using RouteAddress;
    using RouteAddress.Implementations;
    using Serialization;
    public class ConsulMultipleRouteManager : BaseServiceMultipleRouteManager
    {
        private readonly ConsulDispatch _consulDispatch;
        private readonly ServiceDescriptor _serviceDescriptor;
        private readonly ISerializer<string> _serializer;
        private readonly IRouteDescriptorChanger _routeDescriptorChanger;
        private readonly ILogger _logger;
        private readonly RuntimeServiceOption _runtimeServiceOption;
        private readonly ConsulRouteAddressOption _consulRouteAddressOption;


        private const string _keyStoreHeader = "athena_rpc-";
        private Timer _timer;

        private IEnumerable<ServiceMultipleRouteDescriptor> _serviceMultipleRouteDescriptors;

        public ConsulMultipleRouteManager(ConsulDispatch consulDispatch, ServiceDescriptor serviceDescriptor, ISerializer<string> serializer, IRouteDescriptorChanger routeDescriptorChanger, ILogger<ConsulMultipleRouteManager> logger, RuntimeServiceOption runtimeServiceOption, ConsulRouteAddressOption consulRouteAddressOption) : base(routeDescriptorChanger)
        {
            _consulDispatch = consulDispatch;
            _serviceDescriptor = serviceDescriptor;
            _serializer = serializer;
            _routeDescriptorChanger = routeDescriptorChanger;
            _logger = logger;
            _runtimeServiceOption = runtimeServiceOption;
            _consulRouteAddressOption = consulRouteAddressOption;
        }

        public override Task ClearAsync()
        {
            _serviceMultipleRouteDescriptors = null;
            _timer.Dispose();
            return Task.CompletedTask;
        }

        public override async Task<IEnumerable<ServiceMultipleRouteDescriptor>> GetServiceMultipleRouteDescriptorAsync()
        {
            if (_serviceMultipleRouteDescriptors == null)
            {
                _serviceMultipleRouteDescriptors =
                 await GetHealthServiceMultipleRouteDescriptorAsync();
                Watching();
            }
            return _serviceMultipleRouteDescriptors;
        }

        private async Task<IEnumerable<ServiceMultipleRouteDescriptor>> GetHealthServiceMultipleRouteDescriptorAsync()
        {
            var serviceMultipleRouteDescriptorList = new List<ServiceMultipleRouteDescriptor>();

            _logger.LogInformation("Get passing services now...");
            var passingServices = await _consulDispatch.Health.ListChecksInState(new ArguHealth()
            {
                State = ServiceStatus.Passing
            });
            foreach (var item in passingServices)
            {
                if (string.IsNullOrWhiteSpace(item.ServiceID) || !item.ServiceID.StartsWith(_keyStoreHeader) || item.ServiceID.Contains(_serviceDescriptor.Id))
                {
                    continue;
                }
                var resultKVStore = await _consulDispatch.KVStore.Read(new ArguKVStore()
                {
                    Key = item.ServiceID
                });

                if (resultKVStore.Count > 0 && resultKVStore[0].Value.Length > 0)
                {
                    var json = Encoding.UTF8.GetString(resultKVStore[0].Value);
                    var serviceMultipleRouteDescriptor = (ServiceMultipleRouteDescriptor)_serializer.Deserialize(json, typeof(ServiceMultipleRouteDescriptor));
                    _logger.LogInformation($"Get service:{item.ServiceID} route info...");
                    serviceMultipleRouteDescriptorList.Add(serviceMultipleRouteDescriptor);
                }
            }
            return serviceMultipleRouteDescriptorList;
        }

        public override async Task SetServiceMultipleRouteDescriptorAsync(IEnumerable<ServiceMultipleRouteDescriptor> serviceMultipleRouteDescriptors)
        {
            foreach (var item in serviceMultipleRouteDescriptors)
            {
                await SetServiceMultipleRouteDescriptorAsync(item);
            }
        }

        private async Task SetServiceMultipleRouteDescriptorAsync(ServiceMultipleRouteDescriptor serviceMultipleRouteDescriptor)
        {
            var key = $"{_keyStoreHeader}{serviceMultipleRouteDescriptor.ServiceDescriptor.Id}-{serviceMultipleRouteDescriptor.ServiceDescriptor.Node}";

            var tag = _keyStoreHeader + serviceMultipleRouteDescriptor.ServiceDescriptor.Id;

            var arguServiceRegister = new ArguServiceRegister()
            {
                ID = key,
                Name = key,
                Address = _runtimeServiceOption.IP,
                Port = _runtimeServiceOption.Port,
                Tags = new List<string>() { tag },
                Check = new CheckRegister() { }
            };

            if (!string.IsNullOrWhiteSpace(_consulRouteAddressOption.Interval))
            {
                arguServiceRegister.Check.HTTP = _consulRouteAddressOption.HttpHealthUri;
                arguServiceRegister.Check.Interval = _consulRouteAddressOption.Interval;
            }
            else
            {
                arguServiceRegister.Check.TTL = _consulRouteAddressOption.TTL;
            }
            await _consulDispatch.Agent.ServiceRegister(arguServiceRegister);
            if (await _consulDispatch.KVStore.CreateAndUpdate(new ArguKVStore()
            {
                Key = key,
                Value = Encoding.UTF8.GetBytes(_serializer.Serialize(serviceMultipleRouteDescriptor)),
            }))
            {
                _logger.LogInformation($"set {key} success");
            }
        }

        private void Watching()
        {
            var timeSpan = TimeSpan.FromSeconds(_consulRouteAddressOption.CheckIntervalSeconds);
            _timer?.Dispose();
            _timer = new Timer(async callback =>
           {
               var healthServices = await GetHealthServiceMultipleRouteDescriptorAsync();

               var createdServices = new List<ServiceMultipleRouteEventArgs>();
               var changedServices = new List<ServiceMultipleRouteEventArgs>();
               var removedServices = new List<ServiceMultipleRouteEventArgs>();

               var listNoExcept = new List<ServiceMultipleRouteDescriptor>();
               foreach (var healthService in healthServices)
               {
                   var serviceMultipleRouteDescriptor = _serviceMultipleRouteDescriptors.FirstOrDefault(x => x.ServiceDescriptor.Id == healthService.ServiceDescriptor.Id && x.ServiceDescriptor.Node == healthService.ServiceDescriptor.Node);
                   if (serviceMultipleRouteDescriptor != null)
                   {
                       if (!serviceMultipleRouteDescriptor.RouteDescriptors.First().RouteDescriptors.First().Value.Equals(healthService.RouteDescriptors.First().RouteDescriptors.First().Value))
                       {
                           changedServices.Add(new ServiceMultipleRouteChangedEventArgs(healthService.ConvertToServiceMultipleRoute(_routeDescriptorChanger), serviceMultipleRouteDescriptor.ConvertToServiceMultipleRoute(_routeDescriptorChanger)));
                       }
                       listNoExcept.Add(serviceMultipleRouteDescriptor);
                   }
                   else
                   {
                       createdServices.Add(new ServiceMultipleRouteEventArgs(healthService.ConvertToServiceMultipleRoute(_routeDescriptorChanger)));
                   }
               }

               removedServices.AddRange(_serviceMultipleRouteDescriptors.Except(listNoExcept).Select(x => new ServiceMultipleRouteEventArgs(x.ConvertToServiceMultipleRoute(_routeDescriptorChanger))));

               var update = false;
               if (createdServices.Count > 0)
               {
                   OnCreated(createdServices.ToArray());
                   update = true;
               }
               if (changedServices.Count > 0)
               {
                   OnChanged(changedServices.ToArray());
                   update = true;
               }
               if (removedServices.Count > 0)
               {
                   OnRemoved(removedServices.ToArray());
                   update = true;
               }
               if (update)
               {
                   _serviceMultipleRouteDescriptors = healthServices;
               }

           }, null, timeSpan, timeSpan);
        }

    }



}
